﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Xml;
using Newtonsoft.Json;
using System.Xml.Linq;
using Newtonsoft.Json.Linq;
using CarbonExchange.Logger;
using System.Web;
using StructureMap;

namespace CarbonExchange.AmeeQuery
{
    public class AmeeCommand : IDisposable, CarbonExchange.AmeeQuery.IAmeeCommand
    {
        public AmeeCommand()
        {
            settings = new AmeeSettingsReader();
        }

        public AmeeCommand(IAmeeSettingsReader settings)
        {
            this.settings = settings;
        }

        private IAmeeSettingsReader settings;

        private static string AuthenticationKey;

        private HttpWebResponse SendDataRequest(string url, string type, string method, Dictionary<string, string> data, int count)
        {
            if (string.IsNullOrEmpty(AuthenticationKey))
            {
                if (!Authenticate())
                {
                    throw new AmeeConnectionException("Failed to Authenticate");
                }
            }
            string tmpUrl = string.Concat(settings.GetServerUrl(), url);
            HttpWebRequest request = WebRequest.Create(tmpUrl) as HttpWebRequest;
        
            request.Method = method;
            request.Accept = type;
            request.ContentType = "application/x-www-form-urlencoded";
            request.Headers.Add("authToken", AuthenticationKey);

            if (data != null)
            {
                string postData = data.ToFormatedStringWithSpacer("&", "{0}={1}", x => HttpUtility.UrlEncode(x.Key), x => HttpUtility.UrlEncode(x.Value));
                request.ContentLength = postData.Length;

                StreamWriter stOut = new StreamWriter(request.GetRequestStream(), System.Text.Encoding.ASCII);
                stOut.Write(postData);
                stOut.Close();
            }
            StructureMap.ObjectFactory.GetInstance<ILogger>().Debug("Begin Amee {1} Request to '{0}'.", request.Address, request.Method);

            try
            {
                HttpWebResponse response = request.GetResponse() as HttpWebResponse;
                StructureMap.ObjectFactory.GetInstance<ILogger>().Debug("End Amee {2} Request to '{0}' with status '{1}'.", request.Address, response.StatusCode, request.Method);
                return response;
            }
            catch (WebException e)
            {
                HttpWebResponse response = e.Response as HttpWebResponse;
                if (response != null)
                {
                    if (response.StatusCode == HttpStatusCode.Unauthorized)
                    {
                        if (count == 0)
                        {
                            if (ReAuthenticate())
                            {
                                return SendDataRequest(url, type, method, data, count++);
                            }
                            else
                            {
                                throw new AmeeConnectionException("Failed to ReAuthenticate", e);
                            }
                        }
                        else
                        {
                            throw new AmeeConnectionException(string.Format("{1} Request to '{0}' failed. Request Unauthorized", request.Address, request.Method));
                        }
                    }
                    else if (response.StatusCode == HttpStatusCode.NotFound)
                    {
                        StructureMap.ObjectFactory.GetInstance<ILogger>().Warn("{2} Request to '{0}' failed. Response Code '{1}'", request.Address, response.StatusCode, request.Method);
                        return null;
                    }
                    else
                    {
                        throw new AmeeConnectionException(string.Format("{2} Request to '{0}' failed. Response Code '{1}'", request.Address, response.StatusCode, request.Method));
                    }
                }
                else
                {
                    throw new AmeeConnectionException(string.Format("{1} Request to '{0}' failed. No Response", request.Address, request.Method));
                }
            }
            finally
            {
            }
        }

        private HttpWebResponse SendGetRequest(string url, string type, string method, int count)
        {
            if (string.IsNullOrEmpty(AuthenticationKey))
            {
                if (!Authenticate())
                {
                    throw new AmeeConnectionException("Failed to Authenticate");
                }
            }
            HttpWebRequest request = WebRequest.Create(string.Concat(settings.GetServerUrl(), url)) as HttpWebRequest;
            request.Accept = type;
            request.ContentType = type;
            request.Method = method;
            request.Headers.Add("authToken", AuthenticationKey);
            
            StructureMap.ObjectFactory.GetInstance<ILogger>().Debug("Begin Amee Request to '{0}'.", request.Address);
            
            try
            {
                HttpWebResponse response = request.GetResponse() as HttpWebResponse;
                StructureMap.ObjectFactory.GetInstance<ILogger>().Debug("End Amee Request to '{0}' with status '{1}'.", request.Address, response.StatusCode);
                return response;
            }
            catch (WebException e)
            {
                HttpWebResponse response = e.Response as HttpWebResponse;
                if (response != null)
                {
                    if (response.StatusCode == HttpStatusCode.Unauthorized)
                    {
                        if (count == 0)
                        {
                            if (ReAuthenticate())
                            {
                                return SendGetRequest(url, type, method, count++);
                            }
                            else
                            {
                                response.Close();
                                throw new AmeeConnectionException("Failed to ReAuthenticate", e);
                            }
                        }
                        else
                        {
                            response.Close();
                            throw new AmeeConnectionException(string.Format("Request to '{0}' failed. Request Unauthorized", request.Address));
                        }
                    }
                    else if (response.StatusCode == HttpStatusCode.NotFound)
                    {
                        StructureMap.ObjectFactory.GetInstance<ILogger>().Warn("Request to '{0}' failed. Request '{1}'", request.Address, response.StatusCode);
                        response.Close();
                        return null;
                    }
                    else
                    {
                        response.Close();
                        throw new AmeeConnectionException(string.Format("Request to '{0}' failed. Request '{1}'", request.Address, response.StatusCode));
                    }
                }
                else
                {
                    response.Close();
                    throw new AmeeConnectionException(string.Format("Request to '{0}' failed. No Response", request.Address));
                }
            }
            finally
            {
            }
        }

        public bool Authenticate()
        {
            HttpWebRequest request = WebRequest.Create(string.Concat(settings.GetServerUrl(), "auth")) as HttpWebRequest;
            request.Method = "POST";
            request.Accept = "application/xml";
            request.ContentType = "application/x-www-form-urlencoded";
            string postData = string.Format("username={0}&password={1}", settings.GetUsername(), settings.GetPassword());
            request.ContentLength = postData.Length;

            StreamWriter stOut = new StreamWriter(request.GetRequestStream(), System.Text.Encoding.ASCII);
            stOut.Write(postData);
            stOut.Close();

            HttpWebResponse response = request.GetResponse() as HttpWebResponse;
            if (response.Headers["authToken"] != null)
            {
                AuthenticationKey = response.Headers["authToken"].ToString();
                response.Close();
                return true;
            }
            else
            {
                response.Close();
                return false;
            }
        }

        public bool ReAuthenticate()
        {
            AuthenticationKey = null;
            return Authenticate();
        }

        #region IDisposable Members

        public void Dispose()
        {
        }

        #endregion

        public XDocument SendXmlGet(string url)
        {
            ObjectFactory.GetInstance<ILogger>().Info(string.Format("Amee SendXmlGet to '{0}'", url));
            HttpWebResponse response = SendGetRequest(url, "application/xml", "GET", 0);
            
            if (response == null)
            {
                //throw new ApplicationException(string.Format("Failed to get response from '{0}'", url));
                return null;
            }

            try
            {
                StreamReader streamReader = new StreamReader(response.GetResponseStream());
                XDocument doc = XDocument.Load(streamReader);
                return doc;
            }
            finally
            {
                if (response != null)
                {
                    response.Close();
                }
            }
        }

        public JObject SendJsonGet(string url)
        {
            HttpWebResponse response = SendGetRequest(url, "application/json", "GET", 0);
            try
            {
                StreamReader streamReader = new StreamReader(response.GetResponseStream());
                JsonTextReader jReader = new JsonTextReader(streamReader);
                JObject jobj = JObject.Load(jReader);
                return jobj;
            }
            finally
            {
                if (response != null)
                {
                    response.Close();
                }
            }
        }

        public XDocument SendXmlUpdate(string url, Dictionary<string, string> data)
        {
            string tmpUrl = string.Concat(url, "?method=put");
            HttpWebResponse response = SendDataRequest(tmpUrl, "application/xml", "PUT", data, 0);
            try
            {
                StreamReader streamReader = new StreamReader(response.GetResponseStream());
                XDocument doc = XDocument.Load(streamReader);
                return doc;
            }
            finally
            {
                if (response != null)
                {
                    response.Close();
                }
            }
        }

        public XDocument SendXmlInsert(string url, string dataItemId, Dictionary<string, string> data)
        {
            string tmpUrl = string.Concat(url, "?method=post");
            data.Add("dataItemUid", dataItemId);
            HttpWebResponse response = SendDataRequest(tmpUrl, "application/xml", "POST", data, 0);
            try
            {
                StreamReader streamReader = new StreamReader(response.GetResponseStream());
                XDocument doc = XDocument.Load(streamReader);
                return doc;
            }
            finally
            {
                if (response != null)
                {
                    response.Close();
                }
            }
        }

        public XDocument CreateProfile(string name)
        {
            string tmpUrl = "profiles";
            Dictionary<string, string> tmp = new Dictionary<string, string>();
            tmp.Add("profile", "true");
            tmp.Add("name", name);
            HttpWebResponse response = SendDataRequest(tmpUrl, "application/xml", "POST", tmp, 0);
            try
            {
                StreamReader streamReader = new StreamReader(response.GetResponseStream());
                //ToDo:REsolve why it is erroing if passed in stright;
                string tmpString = streamReader.ReadToEnd();
                XDocument doc = XDocument.Parse(tmpString);
                return doc;
            }
            finally
            {
                if (response != null)
                {
                    response.Close();
                }
            }
        }

        public void SendXmlDelete(string url)
        {
            string tmpUrl = string.Concat(url, "?method=delete");
            HttpWebResponse response = SendDataRequest(tmpUrl, "application/xml", "DELETE", null, 0);
            if (response != null)
            {
                response.Close();
            }
        }
    }
}
